package com.snpic.appaw.module.member.service.login;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.BCrypt;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.snpic.appaw.framework.common.constants.SystemConstant;
import com.snpic.appaw.framework.common.enums.UserTypeEnum;
import com.snpic.appaw.framework.common.exception.ServiceException;
import com.snpic.appaw.framework.common.pojo.CommonResult;
import com.snpic.appaw.module.infra.api.config.ConfigApi;
import com.snpic.appaw.module.member.constants.MemberConstant;
import com.snpic.appaw.module.member.controller.app.login.vo.*;
import com.snpic.appaw.module.member.dal.dataobject.login.*;
import com.snpic.appaw.module.member.dal.dataobject.oauth2.OAuth2AccessTokenDO;
import com.snpic.appaw.module.member.dal.mysql.login.*;
import com.snpic.appaw.module.member.dal.mysql.oauth2.OAuth2AccessTokenMapper;
import com.snpic.appaw.module.member.dal.redis.oauth2.OAuth2AccessTokenRedisDAO;
import com.snpic.appaw.module.member.enums.oauth2.OAuth2ClientConstants;
import com.snpic.appaw.module.member.service.oauth2.OAuth2TokenService;
import com.snpic.appaw.module.third.api.jpush.JPushApi;
import com.snpic.appaw.module.third.api.jpush.dto.LoginTokenVerifyReqDTO;
import com.snpic.appaw.module.third.api.jpush.dto.LoginTokenVerifyRespDTO;
import com.snpic.appaw.module.third.api.sms.SmsSenderApi;
import com.snpic.appaw.module.third.api.sms.dto.SmsReqDTO;
import com.snpic.appaw.module.third.api.sms.dto.SmsRespDTO;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.dromara.hutool.core.bean.BeanUtil;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;

import static com.snpic.appaw.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.snpic.appaw.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static com.snpic.appaw.module.member.enums.ErrorCodeConstants.*;

@Service
@Slf4j
public class CustomerServiceImpl implements CustomerService {
	@Resource
	private ConfigApi configInfo;

	@Resource
	private ECustomerMapper eCustomerMapper;

	@Resource
	private RedisTemplate<String, Object> redisTemplate;

	@Resource
	private StringRedisTemplate stringRedisTemplate;
	@Resource
	private LotteryRecordMapper lotteryRecordMapper;

	@Resource
	private InvitedInfoMapper invitedInfoMapper;

	@Resource
	private CompaniesInfoMapper companiesInfoMapper;

	@Resource
	private CustomerServiceManagerMapper customerServiceManagerMapper;

	@Resource
	private ECustomerDeviceManagementMapper eCustomerDeviceManagementMapper;

	@Resource
	private VerificationCodeService verificationCodeService;

	@Resource
	private CiCustomerMapper ciCustomerMapper;


	@Resource
	private ECustomerLoginoutTimeMapper eCustomerLoginoutTimeMapper;

	@Resource
	private SmsSenderApi smsSenderApi;

	@Resource
	private OAuth2TokenService oauth2TokenService;

	@Resource
	private OAuth2AccessTokenMapper oauth2AccessTokenMapper;

	@Resource
	private OAuth2AccessTokenRedisDAO oauth2AccessTokenRedisDAO;

	@Resource
	private JPushApi jPushApi;

	/**
	 * 用户登录
	 */
	@Override
	@Transactional(rollbackFor = Exception.class)
	public LoginRespVO login(LoginReqVO loginReqVO) {
		//积分标志位
		boolean integralFlag = false;
		//创建方法返回值
		LoginRespVO loginRespVO = new LoginRespVO();
		//新用户标志位
		loginRespVO.setNewCustomerFlag(0);
		//用户名,一般为手机号码
		String name = loginReqVO.getName();
		String password = loginReqVO.getPassword();
		String verificationCode = loginReqVO.getVerificationCode();
		String openId = loginReqVO.getOpenId();
		String channelCode = loginReqVO.getChannelCode();
		//极光认证,一键登陆用
		String loginToken = loginReqVO.getLonginToken();

		// 1. 防重放登录校验,TODO 暂时停用
		//checkReplayAttack(loginDTO);
		//登录成功标志位
		boolean loginSuccessFlg = false;
		//1.一键登录认证
		if (StrUtil.isNotBlank(loginToken)) {
			//2.极光认证,一键登录,返回电话号码
			name = oneClickLoginChecker(channelCode, loginToken);
			if (StrUtil.isNotBlank(name)) {
				log.info("一键登录成功,用户={}", name);
				loginSuccessFlg = true;
			}

		}
		// 校验手机号是否为虚拟号码
		checkVirtualNumber(name);

		// 2. 查询用户,条件传入的用户名
		ECustomer customer = eCustomerMapper.selectOne(
			Wrappers.<ECustomer>lambdaQuery().eq(ECustomer::getName, name).eq(ECustomer::getStatus, SystemConstant.StrOne)
		);


		// 处理引流用户,预留扩展
		if (ObjectUtil.isNotNull(customer) && SystemConstant.StrZero.equals(customer.getType())) {
			log.info("用户{}是引流过来的，初次登录更新为引流激活", customer.getName());
			customer.setType(SystemConstant.StrOne);
			int updateCount = eCustomerMapper.updateById(customer);
			if (updateCount != SystemConstant.One) {
				log.error("更新用户{}为引流激活失败", customer.getName());
				throw exception(UPDATE_DRAIN_ACTIVATE);
			}
		}

		if (StrUtil.isNotBlank(password) && !loginSuccessFlg) {
			// 3. 密码登录
			if (ObjectUtil.isNull(customer)) {
				log.error("用户{}密码登录失败，用户不存在", name);
				throw exception(ACCOUNT_OR_PASSWORD_INCORRECT);
			}
			if (StrUtil.isBlank(customer.getPassword())) {
				log.error("用户{}密码登录失败，密码未设置", name);
				throw exception(ACCOUNT_OR_PASSWORD_INCORRECT);
			}

			// 密码错误次数限制
			checkPasswordRetryLimit(name);

			// 通付盾解密
			String decryptedPassword = decryptPassword(loginReqVO, password);

			// 密码校验
			boolean verified = BCrypt.checkpw(decryptedPassword, customer.getPassword());
			if (!verified) {
				incrementLoginFailCount(name);
				throw exception(ACCOUNT_OR_PASSWORD_INCORRECT);
			} else {
				//账户密码登录成功
				loginSuccessFlg = verified;
			}
		} else if (StrUtil.isNotBlank(verificationCode) && !loginSuccessFlg) {
			// 4.验证码登录
			boolean verified = checkVerificationCode(name, verificationCode);
			//验证码校验通过,登录成功
			loginSuccessFlg = verified;
			// 新用户注册
			if (ObjectUtil.isNull(customer)) {
				customer = registerNewUser(loginReqVO, channelCode);
				loginRespVO.setNewCustomerFlag(SystemConstant.One);
				//积分标志位
				integralFlag = true;
			}

			// 是否传入密码,标识
			loginRespVO.setEmptyPWD(StrUtil.isBlank(customer.getPassword()));

			// 设备管理
			saveDeviceInfo(loginReqVO);
		} else {
			throw exception(USER_LOGIN_METHOD_UNKNOWN);
		}

		if (!loginSuccessFlg) {
			//登录失败,抛出错误,退出本方法
			throw exception(USER_LOGIN_FAILED);
		}
		// 登录成功处理
		handleLoginSuccess(customer, openId, channelCode, integralFlag);
		// 获取白名单配置并解析，不限制登录设备的数量
		String whiteListStr = configInfo.getConfigValue("whiteDevicePhone");
		Set<String> whiteList = null;
		if (StrUtil.isNotBlank(whiteListStr)) {
			// 处理分号和逗号分隔的情况
			String[] users = whiteListStr.split(",");
			whiteList = new HashSet<>(Arrays.asList(users));
		}
		boolean isWhitelist = whiteList.contains(name);

		//产生token
		String token = generateToken(loginReqVO, customer.getId(), isWhitelist);

		// 组装返回结果
		customer.setPassword(null);  //脱敏
		loginRespVO.setCustomerId(customer.getId());
		loginRespVO.setEmptyPWD(StrUtil.isBlank(customer.getPassword()));
		loginRespVO.setCustomerInfo(customer);
		loginRespVO.setIsAuth(StrUtil.isNotBlank(customer.getIdNo()) ? "1" : "0");
		loginRespVO.setToken("Bearer " + token);

		return loginRespVO;
	}

	/**
	 * 一键登录校验
	 *
	 * @param channelCode
	 * @param loginToken
	 * @return 电话号码明文
	 */
	private String oneClickLoginChecker(String channelCode, String loginToken) {
		CommonResult<LoginTokenVerifyRespDTO> commonResult = null;
		// 判断H5渠道（101-官网,102-ios,103-安卓,104-鸿蒙,105-小程序,106-移动官网,108-碳谱会）
		Set<String> h5Channels = new HashSet<>(Arrays.asList("101", "105", "106", "108"));
		if (h5Channels.contains(channelCode)) {
			// 调用极光H5验证接口
			commonResult = jPushApi.loginTokenVerify_H5(new LoginTokenVerifyReqDTO().setLoginToken(loginToken));
		}
		// 校验commonResult结果
		if (ObjectUtil.isNotNull(commonResult) && commonResult.getCode() == SystemConstant.Zero && commonResult.getData().getCode() == 0) {
			return commonResult.getData().getPhone();
		} else {
			//一键登录检查失败
			log.error("一键登录检查失败,commonResult={}", commonResult);
			throw exception(ONE_CLICK_LOGIN_CHECK_FAILED);
		}
	}


	/**
	 * 邀请用户注册
	 */
	@Override
	@Transactional(rollbackFor = Exception.class)
	public LoginRespVO invited(InvitedReqVO invitedReqVO) {
		String name = invitedReqVO.getName();
		String verificationCode = invitedReqVO.getVerificationCode();
		// 组装返回结果
		LoginRespVO loginRespVO = new LoginRespVO();
		// 验证码校验
		checkVerificationCode(name, verificationCode);

		// 检查用户是否已注册
		ECustomer existCustomer = eCustomerMapper.selectOne(
			Wrappers.<ECustomer>lambdaQuery().eq(ECustomer::getName, name).eq(ECustomer::getStatus, 1)
		);
		if (ObjectUtil.isNotNull(existCustomer)) {
			throw new ServiceException(300001, "该用户已注册！");
		}

		// 注册新用户
		LocalDateTime nowDate = LocalDateTime.now();
		ECustomer eCustomer = createInvitedCustomer(invitedReqVO, nowDate);
		int count = eCustomerMapper.insert(eCustomer);
		// 2020-12-15 新用户注册送积分
		List<Map<String, String>> integralActivityLst = new ArrayList<Map<String, String>>(); // 积分需求
		Map<String, String> integralActivityMap = new HashMap<String, String>();
		//integralActivityMap.put("activityCode", IntegralActivityCodeConstants.NEWCOMER_REGISTRATION);  TODO
		integralActivityMap.put("activityCode", "activityCode");
		integralActivityMap.put("behaviorTime", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(nowDate));
		;
		integralActivityLst.add(integralActivityMap);
		if (count > 0) {
			//DM-9954-电子保单下载需求
			if ("1".equals(invitedReqVO.getFlag())) {
				ECustomer user = eCustomerMapper.selectOne(
					Wrappers.<ECustomer>lambdaQuery().eq(ECustomer::getName, name).eq(ECustomer::getStatus, 1)
				);
				loginRespVO.setCustomerId(user.getId());
				loginRespVO.setUserCode(user.getName());
			}
			//TODO 缺少integralMallService和integralActivityService
			//UserActivityIntegralRecordDTO userActivityIntegralRecordDTO = new UserActivityIntegralRecordDTO();
			//userActivityIntegralRecordDTO.setUserCode(name);
			//userActivityIntegralRecordDTO.setIntegralActivityLst(integralActivityLst);
			//integralMallService.userActivityIntegralRecord(userActivityIntegralRecordDTO);

			/*
			 * 2021-5-17 邀请人-邀请注册积分【异步】
			 */
			//integralActivityService.sendInviteIntegral(name, basicDto.getBody().get("inviteId").toString(), IntegralActivityCodeConstants.INVITE_REGISTER);
		}

		// 邀请记录
		saveInvitedInfo(invitedReqVO, nowDate);
		// 增加邀请人抽奖次数
		handleLotteryRecord(invitedReqVO, name, nowDate);

		// 绑定客户经理
		bindCustomerManager(invitedReqVO, name, nowDate);


		//loginVO.setCustomerMap(null);

		return loginRespVO;
	}

	/**
	 * 修改指定用户的密码
	 *
	 * @param reqVO
	 * @return
	 */
	@Override
	//@Transactional
	public boolean updatePassword(UpdatePwdReqVO reqVO) {
		short updatePwdType = reqVO.getUpdatePwdType();
		String name = reqVO.getPhone();
		String newPassword = reqVO.getNewPassword();
		String redisKey = MemberConstant.UPDATE_PASSWORD_CODE_TOKEN_PREFIX + name;
		String storedToken = stringRedisTemplate.opsForValue().get(redisKey);
		if (StrUtil.isBlank(storedToken) || !storedToken.equals(reqVO.getTempToken())) {
			log.error("密码修改用临时令牌，校验失败，手机号：{}", name);
			throw exception(TEMP_TOKEN_ERROR);
		}

		// 验证通过后立即删除令牌
		stringRedisTemplate.delete(redisKey);

		if (updatePwdType != 1) {
			log.warn("暂不支持其他方式修改密码");
			throw exception(PASSWORD_UPDATE_FAILED);
		}
		// 1. 查询用户是否存在
		ECustomer customer = eCustomerMapper.selectOne(
			Wrappers.<ECustomer>lambdaQuery().eq(ECustomer::getName, name)
				.eq(ECustomer::getStatus, 1)
		);

		if (ObjectUtil.isNull(customer)) {
			throw exception(USER_NOT_EXIST);
		}

		String phone = customer.getMobile();
		Long customerId = customer.getId();

		// 4. 检查新密码是否与历史密码重复（哈希后比较）
		String storedPassword = customer.getPassword();
		if (StrUtil.isBlank(storedPassword)) {
			// 业务逻辑：原密码为空（首次设置），直接允许设置新密码，无需检查重复
			log.info("用户ID={}首次设置密码，跳过重复检查", customerId);
		} else {
			// 4. 检查新密码newPassword是否与历史密码storedPassword(哈希加密)重复
			boolean isDuplicate = BCrypt.checkpw(newPassword, storedPassword);
			if (isDuplicate) {
				//新密码不能与历史密码相同，请重新设置
				throw exception(CUSTOMER_PASSWORD_DUPLICATE);
			}
		}
		// 5. 更新用户密码
		String newPasswordHash = BCrypt.hashpw(newPassword, BCrypt.gensalt());
		customer.setPassword(newPasswordHash);
		int updated = eCustomerMapper.update(
			null,
			Wrappers.<ECustomer>lambdaUpdate()
				.set(ECustomer::getPassword, newPasswordHash)
				.eq(ECustomer::getId, customerId)
		);
		// 6 发送短信通知用户
		String businessType = "修改密码成功通知";
		//6.2 短信模板所需得参数
		String[] templateParams = {MemberConstant.ONLINE_CUSTOMER_SERVICE_URL};
		SmsReqDTO smsReqDTO = SmsReqDTO.builder().toMobile(phone)
			.businessType(businessType)
			.smsTemplateKey(MemberConstant.SMS_TEMPLATE_UPDATE_PASSWORD_SUCCESS).templateParams(templateParams).build();

		CommonResult<SmsRespDTO> commonResult = smsSenderApi.sendSmsOne(smsReqDTO);
		log.info("调用短信发送接口，手机号：{}，commonResult：{}", phone, commonResult);

		if (commonResult.getCode() != SystemConstant.Zero || !"0000".equals(commonResult.getData().getReplyCode())) {
			log.error("{}短信发送失败，手机号：{}", businessType, phone);
			throw exception(SMS_SEND_FAILED);
		}

		if (updated == 1) {
			return true;
		} else {
			return false;

		}
	}

	/**
	 * 退出登录
	 *
	 * @param token
	 */
	@Override
	public void logout(String token) {
		OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.removeAccessToken(token);
		if (accessTokenDO == null) {
			return;
		}
		// 把登出信息写入数据表
		ECustomer customer = eCustomerMapper.selectById(accessTokenDO.getUserId());
		if (customer == null) {
			return;
		}
		ECustomerLoginoutTimeDO loginoutTimeDO = new ECustomerLoginoutTimeDO();
		loginoutTimeDO.setName(customer.getName());          // 用户名（对应 DO 的 name 字段）
		loginoutTimeDO.setType(SystemConstant.StrTwo);       // 操作类型：2-登出
		loginoutTimeDO.setLoginoroutTime(LocalDateTime.now()); // 登出时间
		loginoutTimeDO.setCreator("system");       // 创建者（当前用户名）
		loginoutTimeDO.setDeleted(false);                    // 未删除
		loginoutTimeDO.setRealName(customer.getRealName());
		loginoutTimeDO.setSex(customer.getSex());
		loginoutTimeDO.setIdNo(customer.getIdNo());
		eCustomerLoginoutTimeMapper.insert(loginoutTimeDO);  // 插入登录流水
	}

	/**
	 * 根据用户名或者当前登录用户获取用户信息
	 *
	 * @param name
	 * @return
	 */
	@Override
	public ECusomterVO getCurrentUser() {
		Long customerId = getLoginUserId();
		ECustomer customer = eCustomerMapper.selectById(customerId);
		return BeanUtil.toBean(customer, ECusomterVO.class);

	}


	// ------------------------------ 私有方法 ------------------------------

	/**
	 * 生成token
	 */
	private String generateToken(LoginReqVO loginReqVO, long customerId, boolean isWhitelist) {
		//实现单点登录，保证该用户的token最新值存入redis,基于OAuth2,删除已经存在的token
		deleteLatestToken(customerId, loginReqVO.getName(), isWhitelist);

		OAuth2AccessTokenDO accessTokenDO = null;
		//普通登录的token有效期是30天
		Integer tokenExpireHours = Integer.parseInt(configInfo.getConfigValue(MemberConstant.TOKEN_EXPIRE_HOURS));
		// 创建访问令牌,有效期30天，权限“cclient:ecustomer”
		if (StrUtil.isBlank(loginReqVO.getPassword())) {
			//密码的登录，toekn有效期不做限制,限制为12个月,根据OAuth2ClientConstants.CCLIENT_UNLIMITED
			accessTokenDO = oauth2TokenService.createAccessToken(customerId, getUserType().getValue(),
				OAuth2ClientConstants.CCLIENT_UNLIMITED, null);
		} else {
			accessTokenDO = oauth2TokenService.createAccessToken(customerId, getUserType().getValue(),
				OAuth2ClientConstants.CCLIENT_COMMON, null);
		}

		//实现单点登录，保证该用户的token最新值存入redis,基于JWT，放弃
		//saveLatestToken(customerId, loginReqVO.getName(), token, tokenExpireHours, isWhitelist);
		return accessTokenDO.getAccessToken();
	}

	private UserTypeEnum getUserType() {

		return UserTypeEnum.CCLIENT;
	}

	/**
	 * 删除指定用户的所有token
	 */
	private void deleteLatestToken(Long customerId, String name, boolean isWhitelist) {

		if (!isWhitelist) {
			//1.清空redis存储的token,非百名单的普通用户
			//1.1 先从数据表中根绝customerId查询出该用户的现有的token
			List<OAuth2AccessTokenDO> oAuth2AccessTokenDOList = oauth2AccessTokenMapper.selectList(Wrappers.<OAuth2AccessTokenDO>lambdaQuery().eq(OAuth2AccessTokenDO::getUserId, customerId));
			if (CollUtil.isNotEmpty(oAuth2AccessTokenDOList)) {
				//1.2 从数据表中查询出的token列表，删除redis中的token
				oAuth2AccessTokenDOList.forEach(oAuth2AccessTokenDO -> {
					oauth2AccessTokenRedisDAO.delete(oAuth2AccessTokenDO.getAccessToken());
					oauth2AccessTokenMapper.deleteById(oAuth2AccessTokenDO.getId());
				});
				log.info("删除用户{}的所有token成功", name);
			}

		}

	}

	/**
	 * 防重放攻击检查
	 */
	private void checkReplayAttack(LoginReqVO loginReqVO) {
		String transTime = loginReqVO.getTransTime();
		String transSerialNumber = loginReqVO.getTransSerialNumber();
		Long difference = null;
		SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");

		try {
			if (StrUtil.isNotBlank(transTime)) {
				difference = System.currentTimeMillis() - Long.parseLong(transTime);
			} else if (StrUtil.isNotBlank(transSerialNumber) && !transSerialNumber.contains("午")) {
				Date date = sdf.parse(transSerialNumber.substring(0, 17));
				difference = System.currentTimeMillis() - date.getTime();
			}

			if (ObjectUtil.isNotNull(difference)) {
				long allowBefore = Long.parseLong(configInfo.getConfigValue("LOGIN_ALLOW_BEFORE_TIME"));
				long allowAfter = Long.parseLong(configInfo.getConfigValue("LOGIN_ALLOW_TIME"));

				if (difference < allowBefore || difference > allowAfter) {
					throw exception(OPERATION_TOO_FREQUENT);
				}
			}
		} catch (Exception e) {
			log.error("防重放攻击检查异常", e);
			throw exception(COMMENT_ERROR_CODE);
		}
	}

	/**
	 * 安全键盘输入的密码解密
	 */
	private String decryptPassword(LoginReqVO loginReqVO, String password) {
		if (loginReqVO.getEncryptKeyboardFlag() != null && SystemConstant.StrOne.equals(loginReqVO.getEncryptKeyboardFlag())) {
			try {
				//TODO 加密键盘功能未实现
				return password;
			} catch (Exception e) {
				throw exception(ENCRYPTED_KEYBOARD_DECODE_FAILED);
			}
		} else {
			//未使用安全加密键盘，直接返回
			return password;
		}
	}

	/**
	 * 密码重试限制检查
	 */
	private void checkPasswordRetryLimit(String name) {
		String key = MemberConstant.RETRY_PASSWORD_PREFIX + name + "_" + DateUtil.format(new Date(), "yyyyMMdd");
		Integer count = (Integer) redisTemplate.opsForValue().get(key);
		count = ObjectUtil.isNotNull(count) ? count : SystemConstant.Zero;
		int maxCount = Integer.parseInt(configInfo.getConfigValue(MemberConstant.LOGIN_PWD_INCORRECT_NUM));
		if (count > maxCount) {
			throw exception(LOGIN_FAILURE_TOO_FREQUENT);
		}
	}

	/**
	 * 增加登录失败次数
	 */
	private void incrementLoginFailCount(String name) {
		String key = MemberConstant.RETRY_PASSWORD_PREFIX + name + "_" + DateUtil.format(new Date(), "yyyyMMdd");
		Long count = redisTemplate.opsForValue().increment(key, SystemConstant.One);
		redisTemplate.expire(key, SystemConstant.One, TimeUnit.DAYS);

	}

	/**
	 * 验证码校验
	 */
	private boolean checkVerificationCode(String name, String verificationCode) {
		boolean verified = verificationCodeService.verifyLoginCode(name, verificationCode);
		if (!verified) {
			throw exception(LOGIN_CODE_FAILED);
		} else {
			return verified;
		}

	}

	/**
	 * 注册新用户
	 */
	private ECustomer registerNewUser(LoginReqVO loginReqVO, String channelCode) {
		LocalDateTime nowDate = LocalDateTime.now();
		// 2020-12-17 积分商城版本之后注册的用户，不初始化历史积分
		ECustomer eCustomer = new ECustomer();
		eCustomer.setName(loginReqVO.getName());
		eCustomer.setMobile(loginReqVO.getName());
		eCustomer.setMobileAuth(SystemConstant.StrOne);// 已认证
		eCustomer.setStatus(SystemConstant.StrOne); // 有效
		eCustomer.setIdAuth(SystemConstant.StrZero);  // 未认证
		eCustomer.setCreator(MemberConstant.AfterIntegralMallVersion);  //AfterIntegralMallVersion
		eCustomer.setRegisterDate(nowDate);
		eCustomer.setUserLevel((long) SystemConstant.Zero);// 用户等级0
		eCustomer.setDrawsAmount((long) SystemConstant.One);  // 新用户注册送抽奖次数1
		eCustomer.setChannelCode(channelCode);
		// 设置用户来源
		setCustomerSource(eCustomer, channelCode, loginReqVO.getOpenId());

		// 记录抽奖流水
		saveLotteryRecord(loginReqVO.getName(), "自主注册");
		int inserted = eCustomerMapper.insert(eCustomer);
		if (inserted == 1) {
			log.info("新用户注册成功，自动生成主键={}", eCustomer.getId());
			return eCustomer;
		} else {
			throw exception(REGISTER_NEW_USER_FAILED);
		}
	}

	/**
	 * 设置用户来源
	 */
	private void setCustomerSource(ECustomer customer, String channelCode, String openId) {
		if (StrUtil.isNotBlank(openId)) {
			if (MemberConstant.CiChannelCode.equals(channelCode)) {  //channelCode：108-碳谱会
				customer.setCustomerSource(MemberConstant.CiSource);  // customerSource:07-碳谱会
			} else {
				customer.setCustomerSource(MemberConstant.WeChatSource);  // 微信来源注册02
				customer.setOpenId(openId);   // 微信端来源openId
			}


		} else if (MemberConstant.WebChannelCode.equals(channelCode)) {  //官网渠道101
			customer.setCustomerSource(MemberConstant.WebSource);  // 官网注册03
		} else if (MemberConstant.MobileWebChannelCode.equals(channelCode)) {  //移动官网渠道106
			customer.setCustomerSource(MemberConstant.MobileWebSource);  // 2021-03-17移动官网注册06
		} else {
			customer.setCustomerSource(MemberConstant.AppSource);  // APP来源01
			customer.setFlag(SystemConstant.StrOne);  // APP注册标志位
		}
	}

	/**
	 * 保存设备信息
	 */
	private void saveDeviceInfo(LoginReqVO loginReqVO) {
		if (ObjectUtil.isNotNull(loginReqVO.getMobileDevice())) {
			String channelCode = loginReqVO.getChannelCode();
			if (MemberConstant.AndroidChannelCode.equals(channelCode) || MemberConstant.IosChannelCode.equals(channelCode)) {
				ECustomerDeviceManagement deviceManagement = new ECustomerDeviceManagement();
				deviceManagement.setPhoneNumber(loginReqVO.getName());
				deviceManagement.setMobileDevice(loginReqVO.getMobileDevice());

				List<ECustomerDeviceManagement> customerDeviceManagements = eCustomerDeviceManagementMapper.selectList(
					Wrappers.<ECustomerDeviceManagement>lambdaQuery()
						.eq(ECustomerDeviceManagement::getPhoneNumber, loginReqVO.getName())
						.eq(ECustomerDeviceManagement::getMobileDevice, loginReqVO.getMobileDevice())
				);
				LocalDateTime now = LocalDateTime.now();
				if (CollUtil.isNotEmpty(customerDeviceManagements)) {
					//设备存在，修改登录日期
					ECustomerDeviceManagement updateDevice = customerDeviceManagements.get(0);
					updateDevice.setUpdateTime(now);
					eCustomerDeviceManagementMapper.updateById(updateDevice);
				} else {
					//设备不存在
					deviceManagement.setDeviceName(loginReqVO.getDeviceName());
					deviceManagement.setDeviceModel(loginReqVO.getDeviceModel());
					deviceManagement.setCityName(loginReqVO.getCityName());
					deviceManagement.setDeleted(false);
					deviceManagement.setCreator(loginReqVO.getName());
					deviceManagement.setCreateTime(now);
					deviceManagement.setUpdateTime(now);
					eCustomerDeviceManagementMapper.insert(deviceManagement);
				}
			}
		}
	}

	/**
	 * 处理登录成功逻辑
	 */
	private void handleLoginSuccess(ECustomer customer, String openId, String channelCode, boolean integralFlag) {
		// 记录登录时间流水
		ECustomerLoginoutTimeDO loginoutTimeDO = new ECustomerLoginoutTimeDO();
		loginoutTimeDO.setName(customer.getName());          // 用户名（对应 DO 的 name 字段）
		loginoutTimeDO.setType(SystemConstant.StrOne);                         // 操作类型：1-登录
		loginoutTimeDO.setLoginoroutTime(LocalDateTime.now()); // 登录时间
		loginoutTimeDO.setCreator("system");       // 创建者（当前用户名）
		loginoutTimeDO.setDeleted(false);                    // 未删除
		loginoutTimeDO.setRealName(customer.getRealName());
		loginoutTimeDO.setSex(customer.getSex());
		loginoutTimeDO.setIdNo(customer.getIdNo());
		eCustomerLoginoutTimeMapper.insert(loginoutTimeDO);  // 插入登录流水

		// 更新碳账户登录时间
		if (StrUtil.isNotBlank(customer.getIdNo())) {
			CiCustomer ciCustomer = ciCustomerMapper.selectOne(
				Wrappers.<CiCustomer>lambdaQuery().eq(CiCustomer::getCertificateNo, customer.getIdNo())
			);
			if (ObjectUtil.isNotNull(ciCustomer) && 1 == ciCustomer.getAccountStatus()) {
				ciCustomer.setLastLoginTime(LocalDate.now());
				ciCustomerMapper.updateById(ciCustomer);
			}
		}


		// 处理openId绑定
		if (StrUtil.isNotBlank(openId)) {
			handleOpenIdBinding(customer, openId, channelCode);
		} else {
			handleAppLogin(customer, channelCode);
		}

	}

	/**
	 * 校验虚拟号码的私有方法
	 *
	 * @param mobile
	 */
	private void checkVirtualNumber(String mobile) {
		String prefixes = configInfo.getConfigValue(MemberConstant.VIRTUAL_NUMBER_PREFIXES);

		// 将逗号分隔的字符串转换为 Set
		Set<String> prefixSet = StrUtil.isNotBlank(prefixes)
			? new HashSet<>(Arrays.asList(prefixes.split(",")))
			: Collections.emptySet();

		if (CollUtil.isNotEmpty(prefixSet)) {
			String prefix = mobile.substring(0, 3);
			if (prefixSet.contains(prefix)) {
				//传入的mobile属于虚拟号码
				throw exception(PHONE_NUMBER_VIRTUAL);
			}
		}
	}

	/**
	 * 处理openId绑定
	 */
	private void handleOpenIdBinding(ECustomer customer, String openId, String channelCode) {
		//channelCode:108-碳谱会，如果是碳谱惠小程序登录，进行redis存储openid操作，供静默登录使用

		if (MemberConstant.CiChannelCode.equals(channelCode)) {  //108碳谱会
			// 碳谱会小程序处理
			String tphOpenidTimeOut = configInfo.getConfigValue(MemberConstant.TPH_OPEN_ID_TIME_OUT);   //tphOpenidTimeOut
			//确认customer.getId()有值
			//将openid作为key放入redis，静默登录直接判断该key是否存在，同时覆盖的作用避免同一设备重复登录
			redisTemplate.opsForValue().set(
				"DefaultLoginKey_" + openId,
				customer.getId().toString(),
				Long.parseLong(tphOpenidTimeOut),
				TimeUnit.HOURS
			);
		} else {
			// 其他渠道openId处理
			// 查询该次登录用户是否与上次一致
			List<ECustomer> openIdList = eCustomerMapper.selectList(
				Wrappers.<ECustomer>lambdaQuery().eq(ECustomer::getOpenId, openId)
			);
			LocalDateTime now = LocalDateTime.now();
			if (CollUtil.isNotEmpty(openIdList) && !customer.getName().equals(openIdList.get(0).getName())) {
				eCustomerMapper.update(
					null,
					Wrappers.<ECustomer>lambdaUpdate()
						.set(ECustomer::getOpenId, null)
						.set(ECustomer::getOpenidChangeTime, now)
						.eq(ECustomer::getOpenId, openId)
				);
			}

			// 更新当前用户openId
			ECustomer record = new ECustomer();
			record.setId(customer.getId());
			record.setOpenId(openId);
			record.setOpenidChangeTime(now);
			if (StrUtil.isNotBlank(openId) && SystemConstant.StrZero.equals(customer.getBindStatus())) {
				record.setBindStatus(SystemConstant.StrOne);
			}
			int updateCount = eCustomerMapper.updateById(record);
		}
	}


	/**
	 * 处理APP登录
	 */
	private void handleAppLogin(ECustomer customer, String channelCode) {
		if ("001".equals(channelCode) || "002".equals(channelCode)
			|| "tianandroidSecretKeyan".equals(channelCode)
			|| "tianiosSecretKeyan".equals(channelCode)) {

			if (!"1".equals(customer.getFlag())) {
				ECustomer updateCustomer = new ECustomer();
				updateCustomer.setId(customer.getId());
				updateCustomer.setFlag("1");
				eCustomerMapper.updateById(updateCustomer);
				//用户来源：01-APP注册;02-微信注册;03-官网注册;04-邀请注册;05-微信邀请注册;06-移动官网注册
				if (MemberConstant.CUSTOMER_SOURCE_INVITED.equals(customer.getCustomerSource())) {
					InvitedInfoDO invitedInfo = new InvitedInfoDO();
					invitedInfo.setInvitedName(customer.getName());
					invitedInfo.setAppFlag(SystemConstant.StrOne);
					invitedInfo.setLoginDate(LocalDateTime.now());
					invitedInfoMapper.update(
						invitedInfo,
						Wrappers.<InvitedInfoDO>lambdaUpdate()
							.eq(InvitedInfoDO::getInvitedName, customer.getName())
					);
				}
			}
		}
	}


	/**
	 * 创建被邀请用户
	 */
	private ECustomer createInvitedCustomer(InvitedReqVO invitedReqVO, LocalDateTime nowDate) {
		ECustomer eCustomer = new ECustomer();
		eCustomer.setName(invitedReqVO.getName());
		eCustomer.setMobile(invitedReqVO.getName());
		eCustomer.setMobileAuth("1");  // 已认证
		eCustomer.setStatus("1"); // 有效
		eCustomer.setIdAuth("0");  // 未认证
		eCustomer.setRegisterDate(nowDate);
		eCustomer.setUserLevel(0L);
		eCustomer.setDrawsAmount(1L);
		eCustomer.setRefereeId(invitedReqVO.getInviteId());
		eCustomer.setCreator("AfterIntegralMallVersion"); // 2020-12-17 积分商城版本之后注册的用户，不初始化历史积分
		// 邀请用户注册标识-05微信邀请
		if (StrUtil.isNotBlank(invitedReqVO.getCustomerSource())) {
			eCustomer.setCustomerSource(invitedReqVO.getCustomerSource());
		} else {
			eCustomer.setCustomerSource("04");
		}

		return eCustomer;
	}

	/**
	 * 保存邀请记录
	 */
	private void saveInvitedInfo(InvitedReqVO invitedReqVO, LocalDateTime nowDate) {
		InvitedInfoDO invitedInfo = new InvitedInfoDO();
		invitedInfo.setInviteId(invitedReqVO.getInviteId());
		invitedInfo.setInvitedName(invitedReqVO.getName());
		invitedInfo.setInvitedDate(LocalDateTime.now());
		invitedInfo.setIsValid(SystemConstant.StrOne);

		if (StrUtil.isNotBlank(invitedReqVO.getCustomerSource())) {
			invitedInfo.setCustomerSource(invitedReqVO.getCustomerSource());
		} else {
			invitedInfo.setCustomerSource(MemberConstant.InviteSource);  //邀请来源，04邀请渠道
		}

		invitedInfoMapper.insert(invitedInfo);
	}

	/**
	 * 处理抽奖记录
	 */
	private void handleLotteryRecord(InvitedReqVO invitedReqVO, String name, LocalDateTime nowDate) {
		// 增加邀请人抽奖次数 TODO sql待确认
		int updateCount = eCustomerMapper.update(
			null,
			Wrappers.<ECustomer>lambdaUpdate()
				.setSql("draws_amount = draws_amount + 1")
				.eq(ECustomer::getName, invitedReqVO.getInviteId())
				.eq(ECustomer::getStatus, "1")
				.eq(ECustomer::getDeleted, 0)
		);
		if (updateCount != 1) {
			log.warn("更新邀请人抽奖次数失败，邀请人name：{}", invitedReqVO.getInviteId());
			throw exception(COMMENT_ERROR_CODE, invitedReqVO.getInviteId());
		}

		// 被邀请人抽奖记录
		saveLotteryRecord(name, "邀请注册", invitedReqVO.getInviteId());

		// 邀请人抽奖记录
		saveLotteryRecord(invitedReqVO.getInviteId(), "邀请他人注册", name);
	}

	/**
	 * 保存抽奖记录
	 */
	private void saveLotteryRecord(String userCode, String node) {
		saveLotteryRecord(userCode, node, null);
	}

	/**
	 * 保存抽奖记录,重载方法
	 */
	private void saveLotteryRecord(String userCode, String node, String invitePhone) {
		LotteryRecordDO lotteryRecord = new LotteryRecordDO();
		lotteryRecord.setCreator(userCode);
		lotteryRecord.setCreateTime(LocalDateTime.now());
		lotteryRecord.setLotteryAmount(1L);
		lotteryRecord.setNode(node);
		lotteryRecord.setUserCode(userCode);
		lotteryRecord.setInvitePhone(invitePhone);
		lotteryRecordMapper.insert(lotteryRecord);
	}

	/**
	 * 绑定客户经理
	 */
	private void bindCustomerManager(InvitedReqVO invitedReqVO, String name, LocalDateTime nowDate) {
		List<CompaniesInfo> companiesInfoList = companiesInfoMapper.selectList(
			Wrappers.<CompaniesInfo>lambdaQuery().eq(CompaniesInfo::getUsercode, invitedReqVO.getInviteId())
		);

		if (CollUtil.isNotEmpty(companiesInfoList)) {
			CustomerServiceManager customerServiceManager = new CustomerServiceManager();
			customerServiceManager.setUserCode(name);
			customerServiceManager.setServiceManagerCode(companiesInfoList.get(0).getUsercode());
			customerServiceManager.setServiceManagerType("1");
			customerServiceManager.setIsValid("1");
			customerServiceManager.setCreateTime(nowDate);
			int count = customerServiceManagerMapper.insert(customerServiceManager);
			if (count > 0) {
				log.info("邀请注册用户：{}，绑定专属客户经理：{}", name, companiesInfoList.get(0).getUsercode());

			}
		}
	}
}